package com.amphora.services.application.impl;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.persistence.Query;

import com.amphora.model.Booking;
import com.amphora.model.CalendarEvent;
import com.amphora.model.Category;
import com.amphora.model.SystemProperty;
import com.amphora.model.Treatment;
import com.amphora.model.TreatmentType;
import com.amphora.model.User;
import com.amphora.services.application.ClientServices;
import com.amphora.services.application.UtilService;
import com.amphora.util.factories.DomainFactory;
import com.amphora.util.repository.JPAUtil;

public class ClientServiceImpl implements ClientServices{

	private UtilService utls;
	
	public ClientServiceImpl() {
		utls = new UtilServiceImpl();
	}
	
	public User registerUser(HashMap<String, String> userDetails) throws Exception{
		String passEcnrypted =  utls.encryptPassword(userDetails.get("password"));
		userDetails.put("password", passEcnrypted);
		
		User newUserToBeRegistered = DomainFactory.getUser(userDetails);
		
		JPAUtil.createEntity(newUserToBeRegistered);
		
		if(JPAUtil.findById(User.class, newUserToBeRegistered.getId())!=null){
			StringBuilder sb = new StringBuilder();
			sb.append("Welcome "+newUserToBeRegistered.getName());
			sb.append("\n\nThis email has been sent as a confirmation of registration.");
			sb.append("\nI sincerely hope you enjoy your experiences with Amphora.");
			sb.append("\n\nKind Regards,");
			sb.append("\n\nAmphora Team");
			utls.sendNotificationEmail(newUserToBeRegistered.getEmailAddress(),newUserToBeRegistered.getName()+" "+newUserToBeRegistered.getSurname(), sb.toString());
			
			return newUserToBeRegistered;
		}
		return newUserToBeRegistered;
	}

	@Override
	public User loginUser(String username, String password) throws Exception{
		String passEcnrypted =  utls.encryptPassword(password);
		return JPAUtil.findSingleItemByQuery(User.class,"from "+User.getClassName()+" where username='"+username+"' and password ='"+passEcnrypted+"'");
	}
	
	@Override
	public String createNewBooking(Calendar start,Long UserID,List<TreatmentType> tt) {
		Long durationForBooking = calculateDurationForBooking(tt);
		Long endInMillis = start.getTimeInMillis() + durationForBooking;
		
		
		Calendar endOfTreatment = Calendar.getInstance();
		endOfTreatment.setTimeInMillis(endInMillis);
		
		if(isTimeSlotAvailable(start, endOfTreatment)){
			User bookingFor = JPAUtil.findById(User.class, UserID);
			if(bookingFor!=null){
				System.out.println("User is logged into session to make booking");
				Booking newBooking = DomainFactory.getBooking("yes", start);
				newBooking.setUser(bookingFor);
				JPAUtil.createEntity(newBooking);
				System.out.println("booking was persisted");
				
				if(JPAUtil.findById(Booking.class, newBooking.getId())!=null){
					System.out.println("Checking booking was persisted correctly: True");
					for(TreatmentType type:tt){
						Treatment treatment = DomainFactory.getTreatment(type.getTreatmentDuration().intValue(), 1);
						treatment.setTreatmentType(type);
						treatment.setBooking(newBooking);
						
						JPAUtil.createEntity(treatment);
						System.out.println("Creating treatment for booking");
					}
					 if(createCalendarEventForBooking(newBooking))
						 return "Booking was successfully created, You will now be redirected to view your booking";
					 else
						 return "Error while creating Booking";
				}else{
					System.out.println("Error: There was a problem during the booking being persisted");
					return "Error: There was a problem during the booking being created";
				}
			}else{
				System.out.println("Error: There was a problem trying to retrieve user from database, During Booking");
				return "Error: There was a problem trying to retrieve user from database, During Booking";
			}
		}else{
			System.out.println("Error: The time slot is not available, please recheck to be sure");
			return "Error: The time slot is not available, please recheck to be sure";
		}
	}

	public boolean createCalendarEventForBooking(Booking newBooking) {
		System.out.println("Creating calendar event for booking");
		List<Treatment> t = JPAUtil.findByQuery("from "+ Treatment.getClassName() +" where booking_id="+newBooking.getId()); 
		
		int totalDurationBooking = 0;
		
		User c = JPAUtil.findById(User.class, newBooking.getUser().getId());
		if(t!=null&&t.size()>0){
			for(Treatment treatment:t)
				totalDurationBooking+=treatment.getDuration();
			
		}
		CalendarEvent approvedDate = createCalendarEventForBooking(newBooking,c,totalDurationBooking);
		
		JPAUtil.createEntity(approvedDate);
		if(JPAUtil.findById(CalendarEvent.class, approvedDate.getId())!=null){
			System.out.println("Sending Notification email");
			return sendNotificationEmail(newBooking,t,c);
		}
		return false;
	}
	
	private CalendarEvent createCalendarEventForBooking(Booking b,User c,int totalDurationBooking){
		System.out.println("Constructing calendar event object");
		Calendar startDate = b.getAppointmentDate();
		long timeInMillis = startDate.getTimeInMillis();
		Calendar endDate = utls.calculateEndOfCalendarEventCal(timeInMillis,totalDurationBooking);
		
		HashMap<String,Calendar> ceDetails = new HashMap<String,Calendar>();
		ceDetails.put("startTime", startDate);
		ceDetails.put("endTime",endDate);
		String title = "Booking for: " + c.getName()+" "+c.getSurname();
		CalendarEvent ce = DomainFactory.getCalendarEvent(ceDetails, title);
		ce.setBooking(b);
		return ce;
	}
	
	private boolean sendNotificationEmail(Booking b,List<Treatment> t, User c){ 
		System.out.println("Sending Notification email for booking made");
		  String from = "noreply.amphora@gmail.com";
	      String to = c.getEmailAddress();
		  final String username = "noreply.amphora";
	      final String password = "amphora2013";
	      
	      Properties props = new Properties();
	      props.put("mail.smtp.auth", "true");
	      props.put("mail.smtp.starttls.enable", "true");
	      props.put("mail.smtp.host", "smtp.gmail.com");
	      props.put("mail.smtp.port", "587");
	      
	      Session session = Session.getInstance(props, new javax.mail.Authenticator(){
	    	 protected PasswordAuthentication getPasswordAuthentication(){
	    		 return new PasswordAuthentication(username, password);
	    	 }
	      });
	      
	      try{
	    	  Message confirmationEmail = new MimeMessage(session);
	    	  
	    	  confirmationEmail.setFrom(new InternetAddress(from));
	    	  
	    	  confirmationEmail.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
	    	  
	    	  confirmationEmail.setRecipient(Message.RecipientType.BCC, new InternetAddress(from));
	    	  
	    	  confirmationEmail.setSubject("Confirmation Email For Booking Made For: "+c.getName()+" "+c.getSurname());
	    		
	    	  StringBuilder sb = new StringBuilder();
	    	  sb.append("Hi "+c.getName()+"<br><br>");
	    	  SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	    	  String bookingDate = format1.format(b.getAppointmentDate().getTime());
	    	  sb.append("The following is sent as a confirmation your booking made for " + bookingDate+"<br><br>");
	    	  sb.append("The treatments select by you are: <br><br>");
	    	  sb.append("<table cellpadding='3'>");
	    	  	sb.append("<tr style='background-color: #1D0772; color: white;font-weight: bold;font-size: 15px; height: 17px;text-align: center;width: 50%;'>");
	    	  		sb.append("<td>");
	    	  			sb.append("Name");
	    	  		sb.append("</td>");
    	  			sb.append("<td>");
    	  				sb.append("Duration");
    	  			sb.append("</td>");
	    	  		sb.append("<td>");
			  			sb.append("Cost");
			  		sb.append("</td>");
	    	  	sb.append("</tr>");
	    	  		int count=0;
	    	  		int total = 0;
		    	  for(Treatment treatments:t){
		    		  count++;
		    		  if(count % 2==0)
		    			  sb.append("<tr style='background-color: #D9EDF7; color: #24526B;font-size: 13px;'>");
		    		  else
		    			 sb.append("<tr style='background-color: #FFFFFF;color: #24526B;font-size: 13px; '>");
			    		
		    		  	 sb.append("<td>");
		    	  		  sb.append(treatments.getTreatmentType().getName());
		    	  		 sb.append("</td>");
		    	  		 sb.append("<td>");
	  	  				 	sb.append(treatments.getDuration() +" minutes");
	  	  				 sb.append("</td>");
		    	  		 sb.append("<td>");
				  			sb.append("R"+treatments.getTreatmentType().getPrice());
				  		 sb.append("</td>");
				  		sb.append("</tr>");
				  		total+=treatments.getTreatmentType().getPrice();
		    	  }
		    	  	  sb.append("<tr>");	
			    	  sb.append("<td>");
			    	  sb.append("&nbsp;");
	    	  		  sb.append("</td>");
	    	  		  sb.append("<td style='background-color: #DDDDDD;color: #333333;font-weight:bold; font-size: 13px;align:right;'>");
	    	  		  sb.append("Total");
	    	  		  sb.append("</td>");
	    	  		  sb.append("<td style='background-color: #DDDDDD;color: #333333;font-size: 13px;align:right;'>");
			  		  sb.append("R"+total);
			  		  sb.append("</td>");
			  		  sb.append("</tr>");
		    	  sb.append("</table>");
		    	  confirmationEmail.setContent(sb.toString(),"text/html");
	    	  
	    	  Transport.send(confirmationEmail);
	    	  
	    	  return true;
	      }catch(Exception e){
	    	  e.printStackTrace();
	    	  return false;
	      }
	}
	
	private long calculateDurationForBooking(List<TreatmentType> tt){
		long durationInMillis = 0;
		for(TreatmentType t:tt)
			durationInMillis += convertToMillis(t.getTreatmentDuration());
		
		return durationInMillis;
	}
	
	private long convertToMillis(Long duration){
		return (duration * 60) * 1000;  
	}
	
	private boolean isTimeSlotAvailable(Calendar start, Calendar end){
		System.out.println("Checking if time slot available");
		
		Calendar now = Calendar.getInstance();
		now.set(Calendar.DAY_OF_WEEK_IN_MONTH, now.get(Calendar.DAY_OF_WEEK_IN_MONTH) - 1);
		now.set(Calendar.HOUR_OF_DAY,23);
		now.set(Calendar.MINUTE,59);
		now.set(Calendar.SECOND,59);
		
		if(start.getTimeInMillis() > now.getTimeInMillis()){
			if(!isOutSideWorkingHours(start,end)){
				if(notOverlapingTreatments(start, end) && notDuringATreatment(start, end)){
					System.out.println("Booking passes business rules");
					return true;
				}
			}else{
				System.out.println("Error: Booking is made outside work hours");
			}
		}else{
			System.out.println("Error: Booking is being made for same day");
		}
		System.out.println("Error: Booking should not come here");
		return false;
	}
	
	private boolean isOutSideWorkingHours(Calendar start, Calendar end){
		HashMap<String,String> systemProperties = new HashMap<String,String>();
		
		Query q = JPAUtil.createQuery("select s from "+SystemProperty.getClassName()+" s");
		
		@SuppressWarnings("unchecked")
		List<SystemProperty> properties   = q.getResultList();
		
		for(SystemProperty s: properties)
			systemProperties.put(s.getName(), s.getValue());
		
		if(systemProperties.get("work_saturday").equals("false")){
			if((int) start.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY)
				return true;
			
		}
		if(systemProperties.get("work_sunday").equals("false")){
			if((int) start.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY)
				return true;
		}
		Integer startHour =  Integer.parseInt(systemProperties.get("start_hour"));
		Integer endHour = Integer.parseInt(systemProperties.get("end_hour"));
		Integer startCalendarHour = start.get(Calendar.HOUR_OF_DAY);  
		Integer endCalendarHour = end.get(Calendar.HOUR_OF_DAY);  
		Integer startCalendarMinute = start.get(Calendar.MINUTE);  
		Integer endCalendarMinute = end.get(Calendar.MINUTE);  
		
		if((startCalendarHour < startHour && startCalendarMinute < 0) || (startCalendarHour > endHour && startCalendarMinute > 0)){
			return true;
		}
		
		if((endCalendarHour < startHour && endCalendarMinute < 0)  || (endCalendarHour > endHour && endCalendarMinute > 0)){
			return true;
		}
		
		return false;
	}

	private boolean notOverlapingTreatments(Calendar start, Calendar end){
		Query q = JPAUtil.createQuery("select ce from " +CalendarEvent.getClassName()+" ce WHERE (ce.startTime BETWEEN :start AND :end) OR (ce.endTime BETWEEN :start AND :end)");
		q.setParameter("start", start);
		q.setParameter("end", end);

		return (q.getResultList()!=null&&q.getResultList().size()>0)?false:true;
	}
	
	private boolean notDuringATreatment(Calendar start, Calendar end){
		Query q = JPAUtil.createQuery("select ce from " +CalendarEvent.getClassName()+" ce WHERE (:start BETWEEN ce.startTime AND ce.endTime) OR (:end BETWEEN ce.startTime AND ce.endTime)");
		q.setParameter("start", start);
		q.setParameter("end", end);

		return (q.getResultList()!=null&&q.getResultList().size()>0)?false:true;
	}


	@Override
	public boolean editUserProfile(Long userIdToEdit) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean deleteUserProfile(Long userIdToRemove) {
		User userToBeDeleted = JPAUtil.findById(User.class, userIdToRemove);
		
		if(userToBeDeleted!=null)
			JPAUtil.delete(userToBeDeleted);
		else
			return false;
		
		return true;
	}

	@Override
	public List<TreatmentType> viewPrices() {
		return JPAUtil.findByQuery("select tt from "+TreatmentType.getClassName()+" tt");
	}

	@Override
	public List<CalendarEvent> viewAllBookingsForMonth() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<Category> viewAllCategories() {
		return JPAUtil.findByQuery("select c from "+Category.getClassName()+" c");
	}
}
